/*
 * @(#)NetworkSession.java 1.0 2009/06/10
 */
package stardust.core.network;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;


/**
 * <code>UDPNetworkSession</code> 类实现维护 UDP 类型网络会话的周期。
 * <p/>
 * @see NetworkState
 * @see TCPNetworkState
 * @version 1.0, 2009/03/28
 * @author ChenChang
 */
public class UDPNetworkSession extends NetworkSession
{

  private DatagramSocket socket;

  public UDPNetworkSession()
  {
    super(ProtocolType.udp);
  }

  public UDPNetworkSession(DatagramSocket socket)
  {
    this();
    this.socket = socket;
  }

  public NetworkState getState()
  {
    if (this.socket == null || (!this.socket.isBound() && (this.socket.isClosed() || !this.socket.isConnected())))
    {
      super.state = NetworkState.closed;
    }

    return super.state;
  }

  public void listen(String address, int port) throws NetworkException
  {
    this.disconnect();

    try
    {
      this.socket = new DatagramSocket(port, InetAddress.getByName(address));
      super.state = NetworkState.open;
    }
    catch(UnknownHostException e)
    {
      this.disconnect();
      throw new NetworkException("无法在 '" + (address == null ? "*" : address) + ":" + port + "' 侦听连接，找不到主机地址。", e);
    }
    catch(IOException e)
    {
      this.disconnect();
      throw new NetworkException("无法在 '" + (address == null ? "*" : address) + ":" + port + "' 侦听连接，I/O 状态异常。", e);
    }
  }

  public NetworkSession accept(int timeout) throws NetworkException
  {
    if (this.getState() != NetworkState.open)
    {
      throw new NetworkException("侦听器处于关闭状态。");
    }

    return this;
  }

  public void connect(String address, int port) throws NetworkException
  {
    this.disconnect();

    try
    {
      this.socket = new DatagramSocket();
      socket.connect(InetAddress.getByName(address), port);
    }
    catch(UnknownHostException e)
    {
      this.disconnect();
      throw new NetworkException("无法与 '" + address + ":" + port + "' 建立连接，找不到主机地址。", e);
    }
    catch(SocketException e)
    {
      this.disconnect();
      throw new NetworkException("无法与 '" + address + ":" + port + "' 建立连接，该端口未开放或其它 I/O 状态异常。", e);
    }
  }

  public void disconnect()
  {
    if (this.socket != null)
    {
      this.socket.close();
      this.socket = null;
    }

    super.state = NetworkState.closed;
  }

  public void apply()
  {
    try
    {
      if (this.socket != null)
      {
        this.socket.setSoTimeout(this.getConfiguration().getTimeout());
      }
    }
    catch(SocketException e)
    {
      throw new NetworkException("无法应用网络变更。", e);
    }
  }

  public int write(byte[] data, int offset, int len) throws NetworkException
  {
    try
    {
      int packetSize = super.getConfiguration().getPacketSize();
      DatagramPacket packet;

      if (len - offset > packetSize)
      {
        throw new NetworkException("UDP报文长度超过最大限制：" + packetSize + "。");
      }
      else
      {
        packet = new DatagramPacket(new byte[len - offset], len - offset);
        this.socket.send(packet);

        return len - offset;
      }
    }
    catch(IOException e)
    {
      throw new NetworkException("写入数据时异常。", e);
    }
  }

  public int write(byte data) throws NetworkException
  {
    try
    {
      DatagramPacket packet = new DatagramPacket(new byte[]
      {
        data
      }, 1);

      this.socket.send(packet);

      return 1;
    }
    catch(IOException e)
    {
      throw new NetworkException("写入数据时异常。", e);
    }
  }

  public int read(byte[] data, int offset, int len) throws NetworkException
  {
    try
    {
      DatagramPacket packet = new DatagramPacket(data, offset, len);

      this.socket.receive(packet);

      return packet.getLength();
    }
    catch(IOException e)
    {
      throw new NetworkException("读取数据时异常。", e);
    }
  }

  public int read() throws NetworkException
  {
    return this.read(new byte[1], 0, 1);
  }

  public String getLocalHost()
  {
    if (this.socket != null)
    {
      return this.socket.getLocalAddress().getHostAddress();
    }

    return null;
  }

  public Integer getLocalPort()
  {
    if (this.socket != null)
    {
      return this.socket.getLocalPort();
    }

    return null;
  }

  public String getRemoteHost()
  {
    if (this.socket != null)
    {
      return this.socket.getInetAddress().getHostAddress();
    }

    return null;
  }

  public Integer getRemotePort()
  {
    if (this.socket != null)
    {
      return this.socket.getPort();
    }

    return null;
  }

}
